

namespace Stee.CAP8.MessageBus.Kafka.Models
{
    using System;
    using System.Collections.Generic;
    using System.Threading.Tasks;
    using NLog;
    using Stee.CAP8.MessageBus.Interfaces.Delegates;
    using Stee.CAP8.MessageBus.Interfaces.Models;
    using Stee.CAP8.MessageBus.Kafka.Utility;

    /// <summary>
    /// Base message handler
    /// </summary>
    /// <typeparam name="T">Message data type: GenericMessage, CommandMessage, EventMessage</typeparam>
    internal abstract class HandlerDetails<T>
    {
        /// <summary>
        /// Abstract logger to support generic handler type
        /// </summary>
        protected abstract Logger NLogger { get; }


        #region Fields

        /// <summary>
        /// Topic to message handler mapping
        /// </summary>
        protected readonly Dictionary<string, HandleSubscribe<T>> Handlers;

        #endregion Fields


        #region Constructor

        /// <summary>
        /// Initializes a new instance of the HandlerDetails class
        /// </summary>
        protected HandlerDetails()
        {
            this.Handlers = new Dictionary<string, HandleSubscribe<T>>();
        }

        #endregion Constructor


        #region Public Methods

        /// <summary>
        /// Add new message handler based on specified topic name
        /// </summary>
        /// <param name="topicName">Topic name</param>
        /// <param name="handler">Message handler</param>
        /// <returns>Return TRUE if added successfully else FALSE</returns>
        internal bool AddHandler(HandleSubscribe<T> handler, string topicName)
        {
            try
            {
                var topic = ValidateHelper.CheckString(topicName);

                if (handler == null)
                {
                    throw new ArgumentNullException($"Null message handler encountered. {nameof(handler)}");
                }

                //// If mapping's handler is empty, it is null. Being null, it is still safe to use (+) operator
                if (this.Handlers.ContainsKey(topic))
                {
                    this.Handlers[topic] += handler;
                }
                else
                {
                    this.Handlers[topic] = handler;
                }

                return true;
            }
            catch (Exception ex)
            {
                NLogger.Error(ex, ex.Message);
                throw;
            }
        }

        /// <summary>
        /// Remove existing message handler based on specified topic name
        /// </summary>
        /// <param name="topicName">Topic name</param>
        /// <param name="handler">Message handler</param>
        /// <returns>Return TRUE if removed successfully else FALSE</returns>
        internal bool RemoveHandler(HandleSubscribe<T> handler, string topicName)
        {
            try
            {
                var topic = ValidateHelper.CheckString(topicName);

                if (handler == null)
                {
                    throw new ArgumentNullException($"Null message handler encountered. {nameof(handler)}");
                }

                //// If mapping's handler is empty, it is null. Being null, it is still safe to use (-) operator
                if (this.Handlers.ContainsKey(topic))
                {
                    this.Handlers[topic] -= handler;
                    return true;
                }
                else
                {
                    NLogger.Warn($"Failed to remove handler with \"{topicName}\" as topic does not exist.");
                    return false;
                }
            }
            catch (Exception ex)
            {
                NLogger.Error(ex, ex.Message);
                throw;
            }
        }

        /// <summary>
        /// Get existing message handler based on specified topic name
        /// </summary>
        /// <param name="topicName">Topic name</param>
        /// <returns>Return topic message handler</returns>
        internal HandleSubscribe<T> GetHandler(string topicName)
        {
            try
            {
                var topic = ValidateHelper.CheckString(topicName);

                //// If mapping's handler is empty, it is null. Being null, it is still safe to use (-) operator
                if (this.Handlers.ContainsKey(topic))
                {
                    return this.Handlers[topic];
                }
                else
                {
                    NLogger.Warn($"Failed to remove handler with \"{topicName}\" as topic does not exist.");
                    return null;
                }
            }
            catch (Exception ex)
            {
                NLogger.Error(ex, ex.Message);
                throw;
            }
        }

        /// <summary>
        /// Invoke message handling
        /// </summary>
        /// <param name="topicName">Message topic name</param>
        /// <param name="message">Message</param>
        /// <param name="publishUtcDT">Message published date time in UTC</param>
        /// <returns>Return TRUE if invoked successfully else FALSE</returns>
        internal abstract bool Invoke(TopicName topicName, T message, DateTime publishUtcDT);

        #endregion Public Methods


        #region Static Methods

        /// <summary>
        /// Generate topic action name
        /// </summary>
        /// <param name="topicName">Topic name</param>
        /// <param name="actionName">Action name</param>
        /// <returns>Return new topic action name</returns>
        protected static string GenerateTopicActionName(string topicName, string actionName)
        {
            return $"{topicName}_{actionName}";
        }

        /// <summary>
        /// Invoke handler asynchronously
        /// </summary>
        /// <typeparam name="TMessage">Message type</typeparam>
        /// <param name="messageHandlers">Handler collection</param>
        /// <param name="topicName">Message topic name</param>
        /// <param name="message">Message</param>
        /// <param name="publishUtcDT">Message published date time in UTC</param>
        protected void HandlerAsync<TMessage>(HandleSubscribe<TMessage> messageHandlers, TopicName topicName, T message, DateTime publishUtcDT)
        {
            if (topicName == null)
            {
                throw new ArgumentNullException(nameof(topicName));
            }
            else if (message == null)
            {
                throw new ArgumentNullException(nameof(message));
            }

            if (messageHandlers != null)
            {
                var messageHandlerList = messageHandlers.GetInvocationList();
                var tasks = new List<Task>(messageHandlerList.Length);

                foreach (var handler in messageHandlerList)
                {
                    if (handler is HandleSubscribe<T>)
                    {
                        //// Invoke
                        var hdr = handler as HandleSubscribe<T>;
                        tasks.Add(Task.Run(() => hdr(topicName, message, publishUtcDT)));
                    }
                    else
                    {
                        NLogger.Warn($"Failed to invoke handler, invalid handler type encountered. Type: {handler.GetType().FullName}");
                    }
                }

                Task.WaitAll(tasks.ToArray());
            }
        }

        #endregion Static Methods
    }
}
